package ofxdownloader;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class is used to parse the pair of input dates used to specify
 * a date range
 * @author Alex Spurling
 *
 */
public class InputDateParser {

	@SuppressWarnings("serial")
	public static class InvalidDateFormatException extends Exception {
		public InvalidDateFormatException(String message) {
			super(message);
		}
	}
	
	/**
	 * Converts a pair of strings representing dates into actual an date
	 * @param fromDateStr
	 * @param toDateStr
	 * @return
	 */
	public static Date getToDate(String fromDateStr, String toDateStr) throws InvalidDateFormatException {
		//If there is no fixed to date then it should be null
		if ("end".equals(toDateStr)) return null;
		//If to date is an actual date then return it
		Date toDate = getDateFromString(toDateStr);
		if (toDate != null) return toDate;

		//if from date is not an actual date then throw an error
		Date fromDate = getDateFromString(fromDateStr);
		if (fromDate == null) throw new InvalidDateFormatException("Invalid pair of dates: from: " + fromDateStr + ", to: " + toDateStr);

		//try to work out the relative date
		toDate = getRelativeDateFromString(toDateStr, fromDate, true);
		if (toDate == null) throw new InvalidDateFormatException("Invalid pair of dates: from: " + fromDateStr + ", to:" + toDateStr);
		
		return toDate;
	}

	/**
	 * Converts a pair of strings representing dates into actual an date
	 * @param fromDateStr
	 * @param toDateStr
	 * @return
	 */
	public static Date getFromDate(String fromDateStr, String toDateStr) throws InvalidDateFormatException {
		//If there is no fixed from date then it should be null
		if ("start".equals(fromDateStr)) return null;
		//If from date is an actual date then return it
		Date fromDate = getDateFromString(fromDateStr);
		if (fromDate != null) return fromDate;

		Date toDate;
		if ("end".equals(toDateStr)) {
			toDate = new Date();
		}else{
			toDate = getDateFromString(toDateStr);
		}
		//if from date is not an actual date then throw an error
		if (toDate == null) throw new InvalidDateFormatException("Invalid pair of dates: from: " + fromDateStr + ", to: " + toDateStr);

		//try to work out the relative date
		fromDate = getRelativeDateFromString(fromDateStr, toDate, false);
		if (fromDate == null) throw new InvalidDateFormatException("Invalid pair of dates: from: " + fromDateStr + ", to: " + toDateStr);
		
		return fromDate;
	}
	
	/**
	 * Parses a date string in the form ddMMMyy into a Date object 
	 * @param dateStr
	 * @return
	 */
	private static Date getDateFromString(String dateStr) {
		SimpleDateFormat format = new SimpleDateFormat("ddMMMyy");
		try {
			return format.parse(dateStr);
		} catch (ParseException e) {
			return null;
		}
	}
	
	/**
	 * Returns the number of days given an input string describing a period
	 * of days, weeks or months. Examples of possible dateStr values and their return values:
	 * "2days",09/06/2008,false returns 07/06/2008
	 * "3weeks",09/06/2008,false returns 19/05/2008
	 * "1month",09/06/2008,true returns 09/07/2008
	 * where dates are shown as dd/mm/yyyy
	 * @param dateStr
	 * @param relativeToDate
	 * @param after whether the returned date is after or before the given date
	 * @return
	 */
	private static Date getRelativeDateFromString(String dateStr, Date relativeToDate, boolean after) {
		Pattern p = Pattern.compile("(\\d+)([a-zA-Z]+?)s?");
		Matcher m = p.matcher(dateStr);
		if (m.matches()) {
			int periodNum = Integer.parseInt(m.group(1));
			//Based on the value of the flag, set the period number
			//to be positive or negative to add or subtract from the given date
			if (!after) periodNum = -periodNum;
			String period = m.group(2).toLowerCase();
			
			Calendar cal = Calendar.getInstance();
			cal.setTime(relativeToDate);
			
			if ("day".equals(period)) {
				cal.add(Calendar.DAY_OF_YEAR, periodNum);
			}else if ("week".equals(period)) {
				cal.add(Calendar.WEEK_OF_YEAR, periodNum);
			}else if ("month".equals(period)) {
				cal.add(Calendar.MONTH, periodNum);
			}else{
				return null;
			}
			return cal.getTime();
		}
		return null;
	}
	
}
